#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tabelaSimbolos.h"
#include "analisadorLexico.h"

//Vetor de lexemas que sera denominado stringLexema.
static char *stringLexema;
//Ponteiro da stringLexema que aponta sempre para o proximo
//bloco disponivel na mesma.
static int ponteiroStringLexema = 0;
//Variavel que mantem o tamanho atual da stringLexema.
static int tamanhoStringLexema;

simbolo* tabelaSimbolos[TAMANHO_PRIMO];

//Funcao HASH encontrada no Livro do Dragao Vermelho.
int funcaoHash(char *c) {
	char *p;
	unsigned h = 0, g;
	for(p = c; *p != '\0'; p = p + 1) {
		h = (h << 4) + (*p);
		if ((g = h & 0xf0000000)) {
			h = h ^ (g >> 24);
			h = h ^ g ;
		}
	}
	return h % TAMANHO_PRIMO;
}

//Realiza uma busca na Tabela de Simbolos, assim como
//especificado: utilizando uma chave de busca do tipo string.
int busca(char* c) {
    //Calcula o Hash de acordo com a entrada.
    int hash = funcaoHash(c);
    simbolo *s;
    //Busca o simbolo na tabela.
    s = tabelaSimbolos[hash];

    //Percorre a lista encadeada.
    while(s != NULL) {
        if(strcmp(c, getLexemaString(s->posicao)) == 0) return s->token;
        s = s->proximo;
    }
    return -1;
}

int contador = 0;
int sizebytes = 0;

int calculaTamanhoRegistro(simbolo* s){
     while (contador < 227){
         simbolo* aux = tabelaSimbolos[contador];
         //char* nome = getLexemaString(s->posicao);
         //fprintf(stdout, "CONTADOR %d\nomen", contador);
         while(aux != NULL) {
            simbolo* doPai = buscaGambi(aux->funcaoPai);

            if(s->tipo == doPai->tipo)  {

                if (aux->tipo == INT) sizebytes += 4;
                else if (aux->tipo == FLOAT) sizebytes += 8;
                else calculaTamanhoRegistro(aux);
                fprintf(stdout, "SISEBYTES: %d", sizebytes);
            }
            aux = aux->proximo;
         }
         contador++;
     }
    return sizebytes;
}

simbolo* buscaSimbolo(char* lex, int escopo) {
    //Calcula o Hash de acordo com a entrada.
    int hash = funcaoHash(lex);
    simbolo *s;
    //Busca o simbolo na tabela.
    s = tabelaSimbolos[hash];
    //Percorre a lista encadeada.
    while(s != NULL) {
        if(strcmp(lex, getLexemaString(s->posicao)) == 0) {
            if(s->escopo <= escopo) {
                return s;
            }
        }
        s = s->proximo;
    }
    return s;
}

simbolo* buscaSimboloPai(char* lex, char* funcaoPai) {
    //Calcula o Hash de acordo com a entrada.
    int hash = funcaoHash(lex);
    simbolo *s;
    //Busca o simbolo na tabela.
    s = tabelaSimbolos[hash];
    //Percorre a lista encadeada.
    while(s != NULL) {
        if(strcmp(lex, getLexemaString(s->posicao)) == 0) {
            if(strcmp(funcaoPai, s->funcaoPai) == 0) {
                return s;
            }
        }
        s = s->proximo;
    }
    return s;
}

simbolo* buscaGambi (char* lex){
    int hash = funcaoHash(lex);
    simbolo *s;
    //Busca o simbolo na tabela.
    s = tabelaSimbolos[hash];
    //Percorre a lista encadeada.
    while(s != NULL) {
        if(strcmp(lex, getLexemaString(s->posicao)) == 0) {
                return s;
        }
        s = s->proximo;
    }
    return s;
}

char* buscaNomePai(int tipo) {
    int contador = 0;
    while(contador < TAMANHO_PRIMO) {
        simbolo* s = tabelaSimbolos[contador];

        while(s != NULL) {
            if(s->tipo == tipo && s->typeDecl == EH_TYPEDECL) {
                return getLexemaString(s->posicao);
            }
            s = s->proximo;
        }
        contador++;
    }
    return NULL;
}

//retorna o pointeiro da stringLexema.
int getPonteiroStringLexema() {
    return ponteiroStringLexema;
}

//Adiciona um lexema ao vetor de lexemas caracter por caracter.
void adicionaLexemaString(char* c) {
    int tamanhoFinalStringLexema = strlen(c) + ponteiroStringLexema;
    while(tamanhoFinalStringLexema > tamanhoStringLexema) {
        expandeLexemaString();
    }
    unsigned int contador = 0;
    while(contador < strlen(c)) stringLexema[ponteiroStringLexema++] = c[contador++];
    stringLexema[ponteiroStringLexema++] = '\0';
}

//Realoca a stringLexema provendo mais espaco para a mesma.
void expandeLexemaString() {
    stringLexema = (char*) realloc(stringLexema, sizeof(char) * (tamanhoStringLexema + TAMANHO_CRESC_STRING_LEXEMA));
    if(stringLexema == NULL) free(stringLexema);
    tamanhoStringLexema = tamanhoStringLexema + TAMANHO_CRESC_STRING_LEXEMA;
}

//Retorna a string presente em stringLexema de acordo com
//um ponteiro que indica a posicao da mesma.
char* getLexemaString(unsigned int inicio) {
    return stringLexema + inicio;
}

//Realiza a funcao de construtor do Gerenciador de Caracteres,
//inicialmente armazenando o tamanho atual da stringLexema
//e alocando espaco na memoria utilizando malloc.
//Por fim realiza a adicao das palavras reservadas da
//linguagem utilizando o metodo definido anteriormente.
void inicializaStringLexema() {
    tamanhoStringLexema = TAMANHO_STRING_LEXEMA;
    stringLexema = (char*)malloc(sizeof(char)*tamanhoStringLexema);
    if(stringLexema == NULL) free(stringLexema);
    adicionaLexema("typedef", TYPEDEF, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("struct", STRUCT, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("int", INT, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("float", FLOAT, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("boolean", BOOLEAN, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("if", IF, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("else", ELSE, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("while", WHILE, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("switch", SWITCH, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("break", BREAK, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("println", PRINTLN, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("readln", READLN, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("return", RETURN, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("case", CASE, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("true", TRUE, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
    adicionaLexema("false", FALSE, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, NULL, SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
}

//Limpa a stringLexema da memoria fazendo uso da funcao free.
void limpaTabela() {
    free(stringLexema);
    int contador = 0;
    while(contador < TAMANHO_PRIMO) {
        simbolo* s = tabelaSimbolos[contador];
        while(s != NULL) {
            simbolo* temp = s->proximo;
            free(s);
            s = temp;
        }
        tabelaSimbolos[contador] = NULL;
        contador++;
    }
}

//Adiciona um Lexema na Tabela de Simbolos.
int adicionaLexema(char* lex, int token, int escopo, int tipo, int funcao, char* funcaoPai, int status, int typeDecl, int array) {
    int lexemaBuscado = busca(lex);
    if(lexemaBuscado == -1 || escopo != SEM_ESCOPO) {
        simbolo *novoSimbolo;
        int posicaoHash = funcaoHash(lex);
        novoSimbolo = (simbolo*) malloc(sizeof(simbolo));
        novoSimbolo->token = token;
        novoSimbolo->posicao = getPonteiroStringLexema();
        novoSimbolo->escopo = escopo;
        novoSimbolo->status = status;
        novoSimbolo->tipo = tipo;
        novoSimbolo->funcao = funcao;
        novoSimbolo->typeDecl = typeDecl;
        novoSimbolo->array = array;
        if(funcaoPai != NULL) novoSimbolo->funcaoPai = funcaoPai;
        else novoSimbolo->funcaoPai = "$global";
        novoSimbolo->parametros = NULL;
        adicionaLexemaString(lex);
        novoSimbolo->proximo = tabelaSimbolos[posicaoHash];
        tabelaSimbolos[posicaoHash] = novoSimbolo;
        return token;
    }
    return lexemaBuscado;
}

//Metodo apenas de auxilio para impressao da Tabela de Simbolos.
void imprimeCasosEspeciais(simbolo* s) {
    unsigned int contador = 0;
    char* lex = getLexemaString(s->posicao);
    fprintf(stdout, "%-10s", nomeToken(s->token));
    for(; contador < strlen(lex);) {
        fprintf(stdout, "%c", lex[contador++]);
        if(contador % 45 == 0) {
            if(contador == 45) fprintf(stdout, "%-5s%d", "", s->token);
            fprintf(stdout, "\n");
            fprintf(stdout, "%-10s", "");
        }
    }
    fprintf(stdout, "\n");
}

//Marca todos os simbolos que estiverem no escopo
//passado por parametro como inativos.
void desativaSimbolosEscopo(int escopo) {
    int contador = 0;
    while(contador < TAMANHO_PRIMO) {
        simbolo* s = tabelaSimbolos[contador];
        while(s != NULL) {
            if(s->escopo == escopo) {
                s->status = SIMBOLO_INATIVO;
            }
            s = s->proximo;
        }
        contador++;
    }
}

//Imprime a Tabela de Simbolos.
void imprimeTabelaSimbolos() {
    fprintf(stdout, "--------------------------------------------------------------\n");
    fprintf(stdout, "-------------- TABELA DE SIMBOLOS - AMARAL V2.0 --------------\n");
    fprintf(stdout, "--------------------------------------------------------------\n");
    fprintf(stdout, "%-10s%-44s%-3s\n", "Token", "Atributo", "Numerico");
    fprintf(stdout, "--------------------------------------------------------------\n");
    int i = 0;
    for(; i < TAMANHO_PRIMO; i++) {
        simbolo *s = tabelaSimbolos[i];
        while(s != NULL) {
            if(palavraReservada(s->token)) {
                //Somente os IDs e Literais devem ser impressos na Tabela de Simbolos.
                //Sendo assim, caso o token recebido seja maior ou igual a TYPEDEF
                //que eh o primeiro dos tokens de palavras definidos pelo Compilador Amaral,
                //imprime-se apenas seu nome e seu numero.
                fprintf(stdout, "%-10s%-50s%d\n", nomeToken(s->token), "", s->token);
            } else {
                //Aqui ja imprime-se tanto o nome do token (que sera ID ou Literal) seguido
                //do lexema e seu numero.
                if(strlen(getLexemaString(s->posicao)) > 45) {
                    //Caso o tamanho do lexema extrapole os limites da coluna,
                    //utiliza-se o metodo imprimeCasosEspeciais que faz o ajuste
                    //para que ele caiba na coluna.
                    imprimeCasosEspeciais(s);
                } else {
                    fprintf(stdout, "%-10s%-50s%d\n", nomeToken(s->token), getLexemaString(s->posicao), s->token);
                }
            }
            //Obtem-se o proximo simbolo.
            s = s->proximo;
        }
    }
    fprintf(stdout, "--------------------------------------------------------------\n");
}

